/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.apisupport;
import java.awt.*;
import java.beans.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
import javax.swing.JPanel;
import org.openide.*;
import org.openide.actions.*;
import org.openide.awt.SplittedPanel;
import org.openide.cookies.InstanceCookie;
import org.openide.execution.NbClassLoader;
import org.openide.execution.NbClassPath;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerPanel;
import org.openide.explorer.propertysheet.PropertySheetView;
import org.openide.explorer.view.BeanTreeView;
import org.openide.filesystems.*;
import org.openide.loaders.*;
import org.openide.nodes.*;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.*;
/** Helps set various otherwise hidden template properties used by {@link TemplateWizard}.
* @author Jesse Glick
*/
public class TemplateWizardHelperAction extends CookieAction {
/** Attribute used by SystemFileSystem to look for a localizing bundle. */
private final static String ATTR_BUNDLE = "SystemFileSystem.localizingBundle";
private static final long serialVersionUID =-536850488915983782L;
protected Class[] cookieClasses () {
return new Class[] { DataObject.class };
}
protected int mode () {
return MODE_EXACTLY_ONE;
}
public static class MyExplorerPanel extends ExplorerPanel {
private static final long serialVersionUID = -5685493348821098378L;
public MyExplorerPanel () {
setName ("Installable File Helper");
setLayout (new BorderLayout ());
SplittedPanel split = new SplittedPanel ();
split.setSplitType (SplittedPanel.HORIZONTAL);
split.add (new BeanTreeView (), SplittedPanel.ADD_LEFT);
split.add (new PropertySheetView (), SplittedPanel.ADD_RIGHT);
add (split, BorderLayout.CENTER);
}
protected void updateTitle () {
// Do nothing.
}
}
/** Will open a dialog showing the enhanced property sheet for the template file. */
protected void performAction (Node[] nodes) {
Node n = nodes[0];
DataObject dob = (DataObject) n.getCookie (DataObject.class);
if (dob == null) return;
ExplorerPanel panel = new MyExplorerPanel ();
Node philtre = new Filter (n, dob);
panel.getExplorerManager ().setRootContext (philtre);
try {
panel.getExplorerManager ().setSelectedNodes (new Node[] { philtre });
} catch (PropertyVetoException pve) {
pve.printStackTrace ();
}
panel.open ();
panel.requestFocus ();
}
public String getName () {
return "Installable File Helper ...";
}
protected String iconResource () {
return "resources/TemplateWizardHelperIcon.gif";
}
public HelpCtx getHelpCtx () {
return new HelpCtx ("org.netbeans.modules.apisupport.utils.tmplwizhlpr");
}
/** Only enabled on templates. */
protected boolean enable (Node[] nodes) {
if (! super.enable (nodes)) return false;
DataObject dob = (DataObject) nodes[0].getCookie (DataObject.class);
return dob.isTemplate () || dob.getPrimaryFile ().isFolder ();
}
/** The dummy node used to display a property sheet of. */
private static class Filter extends FilterNode {
DataObject dob;
public Filter (Node orig, DataObject dob) {
super (orig, (dob instanceof DataFolder) ? new FilterChildren (orig) : Children.LEAF);
this.dob = dob;
}
private static class MyHandle implements Node.Handle {
private Node.Handle orig;
private DataObject obj;
private static final long serialVersionUID = 6315937780815111350L;
public MyHandle (Node origNode, DataObject obj) {
orig = origNode.getHandle ();
this.obj = obj;
}
public Node getNode () throws IOException {
return new Filter (orig.getNode (), obj);
}
}
public Node.Handle getHandle () {
return new MyHandle (getOriginal (), dob);
}
public SystemAction[] getActions () {
return new SystemAction[] {
SystemAction.get (PopulateLocBundleAction.class),
null,
SystemAction.get (ToolsAction.class),
SystemAction.get (PropertiesAction.class),
};
}
private final Node.Property iteratorProp = new IteratorProp ();
private final Node.Property urlProp = new UrlProp ();
private final Node.Property rsrcProp = new RsrcProp ();
private final Node.Property locBundleProp = new LocBundleProp ();
private final Node.Property locNameProp = new LocNameProp ();
public Node.PropertySet[] getPropertySets () {
Node.PropertySet[] orig = super.getPropertySets ();
Node.PropertySet[] nue = new Node.PropertySet[orig.length + 1];
System.arraycopy (orig, 0, nue, 1, orig.length);
nue[0] = new Node.PropertySet ("tmplwizhlpr", "Extra", "Extra properties applicable to templates and installable files.") {
public Node.Property[] getProperties () {
return new Node.Property[] { iteratorProp, urlProp, rsrcProp, locBundleProp, locNameProp };
}
};
return nue;
}
/** A property permitting the {@link TemplateWizard.Iterator} to be set for a template. */
private final class IteratorProp extends PropertySupport.ReadWrite {
public IteratorProp () {
super ("iterator",
TemplateWizard.Iterator.class,
"Sequence of Panels",
"An object implementing TemplateWizard.Iterator representing the template panels.");
}
public Object getValue () {
return TemplateWizard.getIterator (dob);
}
public void setValue (Object val) throws InvocationTargetException {
try {
TemplateWizard.setIterator (dob, (TemplateWizard.Iterator) val);
} catch (IOException ioe) {
throw new InvocationTargetException (ioe);
}
}
public boolean supportsDefaultValue () {
return true;
}
public void restoreDefaultValue () throws InvocationTargetException {
setValue (null);
}
public PropertyEditor getPropertyEditor () {
return new IteratorPropEd ();
}
}
/** A property for a template's description URL. */
private final class UrlProp extends PropertySupport.ReadWrite {
public UrlProp () {
super ("url", URL.class, "Description URL", "Raw URL to a description page.");
}
public Object getValue () {
return TemplateWizard.getDescription (dob);
}
public void setValue (Object val) throws InvocationTargetException {
try {
TemplateWizard.setDescription (dob, (URL) val);
} catch (IOException ioe) {
throw new InvocationTargetException (ioe);
}
}
public boolean supportsDefaultValue () {
return true;
}
public void restoreDefaultValue () throws InvocationTargetException {
setValue (null);
}
}
// accessor method
void fPC (String prop, Object old, Object nue) {
firePropertyChange (prop, old, nue);
}
/** A property for a template's description, considered as a classloader resource path. */
private final class RsrcProp extends PropertySupport.ReadWrite {
public RsrcProp () {
super ("resourceDesc", String.class, "Description Resource", "Resource path to a description page.");
}
public Object getValue () {
return TemplateWizard.getDescriptionAsResource (dob);
}
public void setValue (Object val) throws InvocationTargetException {
try {
TemplateWizard.setDescriptionAsResource (dob, (String) val);
// The resourceDesc property will auto-fire changes, but changing it also may affect url:
fPC ("url", null, null);
} catch (IOException ioe) {
ioe.printStackTrace ();
throw new InvocationTargetException (ioe);
} catch (RuntimeException re) {
re.printStackTrace ();
throw re;
}
}
public boolean supportsDefaultValue () {
return true;
}
public void restoreDefaultValue () throws InvocationTargetException {
setValue (null);
}
public PropertyEditor getPropertyEditor () {
return new RsrcPropEd ();
}
}
/** Property for the bundle file holding the file's localized name. */
private final class LocBundleProp extends PropertySupport.ReadWrite {
public LocBundleProp () {
super ("locBundle", String.class, "Localizing Bundle", "Resource name of a bundle giving localized file names.");
}
public Object getValue () {
return dob.getPrimaryFile ().getAttribute (ATTR_BUNDLE);
}
public void setValue (Object val) throws InvocationTargetException {
try {
dob.getPrimaryFile ().setAttribute (ATTR_BUNDLE, val);
// If set, may affect the name:
fPC ("locName", null, null);
} catch (IOException ioe) {
ioe.printStackTrace ();
throw new InvocationTargetException (ioe);
}
}
public boolean supportsDefaultValue () {
return true;
}
public void restoreDefaultValue () throws InvocationTargetException {
setValue (null);
}
public PropertyEditor getPropertyEditor () {
return new LocBundlePropEd ();
}
}
/** Property for the localized name of the file, if any. */
private final class LocNameProp extends PropertySupport.ReadOnly {
public LocNameProp () {
super ("locName", String.class, "Localized Name", "Localized name of this file in the current locale.");
}
public Object getValue () {
FileObject fo = dob.getPrimaryFile ();
String foName = fo.getPackageNameExt ('/', '.');
String bundleName = (String) fo.getAttribute (ATTR_BUNDLE);
if (bundleName == null) return "<unlocalized>";
// Note: using (+/-) currentClassLoader for testing purposes, though at deploy time
// only systemClassLoader is used.
// NbClassLoader is needed because modifying the bundle does not seem to
// properly refresh the currentClassLoader.
// Also, first attempt tries to load from Repo even if this overrides
// something in the system classpath (as is common in this case).
// Second attempt falls back to IDE module classloader.
ResourceBundle bundle;
try {
bundle = NbBundle.getBundle (bundleName, Locale.getDefault (),
new NbClassLoader (TopManager.getDefault ().getRepository ().toArray (), null));
} catch (MissingResourceException mre) {
try {
bundle = NbBundle.getBundle (bundleName, Locale.getDefault (),
TopManager.getDefault ().systemClassLoader ());
} catch (MissingResourceException mre2) {
return "<bundle not found>";
}
}
try {
return bundle.getString (foName);
} catch (MissingResourceException mre) {
//System.err.println ("Keys:");
//Enumeration e = bundle.getKeys ();
//while (e.hasMoreElements ())
// System.err.println ("\t" + e.nextElement ());
return "<key " + foName + " not found>";
}
}
}
}
private static class FilterChildren extends FilterNode.Children {
public FilterChildren (Node orig) {
super (orig);
}
protected Node[] createNodes (Object key) {
DataObject dob = (DataObject) ((Node) key).getCookie (DataObject.class);
return new Node[] { new Filter ((Node) key, dob) };
}
}
/** Custom property editor for the iterator.
* Displays current value as an object, but can set a class name.
* Custom editor also permits you to select the instance (usually Java source)
* to use for the iterator.
*/
private static final class IteratorPropEd extends PropertyEditorSupport {
public String getAsText () {
return String.valueOf (getValue ());
}
public void setAsText (String text) throws IllegalArgumentException {
try {
setValue ("null".equals (text) ?
null :
Beans.instantiate (TopManager.getDefault ().currentClassLoader (), text));
} catch (Exception e) {
throw new IllegalArgumentException (e.toString ());
}
}
public boolean supportsCustomEditor () {
return true;
}
public Component getCustomEditor () {
ExplorerPanel panel = new ExplorerPanel ();
panel.setLayout (new BorderLayout ());
panel.add (new BeanTreeView (), BorderLayout.CENTER);
ExplorerManager mgr = panel.getExplorerManager ();
mgr.setRootContext (TopManager.getDefault ().getPlaces ().nodes ().repository ());
mgr.addVetoableChangeListener (new VetoableChangeListener () {
public void vetoableChange (PropertyChangeEvent ev) throws PropertyVetoException {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
Node[] nodes = (Node[]) ev.getNewValue ();
if (nodes == null || nodes.length > 1) throw new PropertyVetoException ("Must select one node", ev);
if (nodes.length == 0) return;
InstanceCookie inst = (InstanceCookie) nodes[0].getCookie (InstanceCookie.class);
try {
if (inst == null || ! TemplateWizard.Iterator.class.isAssignableFrom (inst.instanceClass ()))
throw new PropertyVetoException ("Must be a TemplateWizard.Iterator", ev);
} catch (Exception e) {
throw new PropertyVetoException (e.toString (), ev);
}
}
}
});
mgr.addPropertyChangeListener (new PropertyChangeListener () {
public void propertyChange (PropertyChangeEvent ev) {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
Node[] nodes = (Node[]) ev.getNewValue ();
if (nodes.length == 0) return;
InstanceCookie inst = (InstanceCookie) nodes[0].getCookie (InstanceCookie.class);
try {
setValue (inst.instanceCreate ());
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
JPanel outpanel = new JPanel ();
outpanel.setLayout (new BorderLayout ());
outpanel.add (panel, BorderLayout.CENTER);
HelpCtx.setHelpIDString (outpanel, "org.netbeans.modules.apisupport.utils.tmplwizhlpr");
return outpanel;
}
}
/** Special property editor for the description as a resource path.
* Can directly type in the value, or browse to an HTML file in the Repository.
*/
private static final class RsrcPropEd extends PropertyEditorSupport {
public String getAsText () {
String val = (String) getValue ();
return (val == null) ? "null" : val;
}
public void setAsText (String text) {
setValue ("null".equals (text) ? null : text);
}
public boolean supportsCustomEditor () {
return true;
}
private boolean isHtml (DataObject dob) {
return "text/html".equals (dob.getPrimaryFile ().getMIMEType ());
}
public Component getCustomEditor () {
ExplorerPanel panel = new ExplorerPanel ();
panel.setLayout (new BorderLayout ());
panel.add (new BeanTreeView (), BorderLayout.CENTER);
ExplorerManager mgr = panel.getExplorerManager ();
mgr.setRootContext (TopManager.getDefault ().getPlaces ().nodes ().repository (new DataFilter () {
public boolean acceptDataObject (DataObject dob) {
return (dob.getCookie (DataFolder.class) != null) || isHtml (dob);
}
}));
mgr.addVetoableChangeListener (new VetoableChangeListener () {
public void vetoableChange (PropertyChangeEvent ev) throws PropertyVetoException {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
Node[] nodes = (Node[]) ev.getNewValue ();
if (nodes == null || nodes.length > 1) throw new PropertyVetoException ("Must select one node", ev);
if (nodes.length == 0) return;
DataObject dob = (DataObject) nodes[0].getCookie (DataObject.class);
if (dob == null) throw new PropertyVetoException ("Must select a file", ev);
if (! isHtml (dob)) throw new PropertyVetoException ("Must be an HTML file", ev);
}
}
});
mgr.addPropertyChangeListener (new PropertyChangeListener () {
public void propertyChange (PropertyChangeEvent ev) {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
Node[] nodes = (Node[]) ev.getNewValue ();
if (nodes.length == 0) return;
DataObject dob = (DataObject) nodes[0].getCookie (DataObject.class);
if (dob == null || ! isHtml (dob)) return;
setValue (dob.getPrimaryFile ().getPackageNameExt ('/', '.'));
}
}
});
JPanel outpanel = new JPanel ();
outpanel.setLayout (new BorderLayout ());
outpanel.add (panel, BorderLayout.CENTER);
HelpCtx.setHelpIDString (outpanel, "org.netbeans.modules.apisupport.utils.tmplwizhlpr");
return outpanel;
}
}
/** Special property editor for bundle files.
* Can directly type in the value, or browse to a .properties file in the Repository.
*/
private static final class LocBundlePropEd extends PropertyEditorSupport {
public String getAsText () {
String val = (String) getValue ();
return (val == null) ? "null" : val;
}
public void setAsText (String text) {
setValue ("null".equals (text) ? null : text);
}
public boolean supportsCustomEditor () {
return true;
}
private boolean isProperties (DataObject dob) {
return "properties".equals (dob.getPrimaryFile ().getExt ());
}
public Component getCustomEditor () {
ExplorerPanel panel = new ExplorerPanel ();
panel.setLayout (new BorderLayout ());
panel.add (new BeanTreeView (), BorderLayout.CENTER);
ExplorerManager mgr = panel.getExplorerManager ();
mgr.setRootContext (TopManager.getDefault ().getPlaces ().nodes ().repository (new DataFilter () {
public boolean acceptDataObject (DataObject dob) {
return (dob.getCookie (DataFolder.class) != null) || isProperties (dob);
}
}));
mgr.addVetoableChangeListener (new VetoableChangeListener () {
public void vetoableChange (PropertyChangeEvent ev) throws PropertyVetoException {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
Node[] nodes = (Node[]) ev.getNewValue ();
if (nodes == null || nodes.length > 1) throw new PropertyVetoException ("Must select one node", ev);
if (nodes.length == 0) return;
DataObject dob = (DataObject) nodes[0].getCookie (DataObject.class);
if (dob == null) throw new PropertyVetoException ("Must select a file", ev);
if (! isProperties (dob)) throw new PropertyVetoException ("Must be a properties file", ev);
}
}
});
mgr.addPropertyChangeListener (new PropertyChangeListener () {
public void propertyChange (PropertyChangeEvent ev) {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
Node[] nodes = (Node[]) ev.getNewValue ();
if (nodes.length == 0) return;
DataObject dob = (DataObject) nodes[0].getCookie (DataObject.class);
if (dob == null || ! isProperties (dob)) return;
// [PENDING] could in principle fail if there was a bundle
// that did not have a default-locale file as its primary file
setValue (dob.getPrimaryFile ().getPackageName ('.'));
}
}
});
JPanel outpanel = new JPanel ();
outpanel.setLayout (new BorderLayout ());
outpanel.add (panel, BorderLayout.CENTER);
HelpCtx.setHelpIDString (outpanel, "org.netbeans.modules.apisupport.utils.tmplwizhlpr");
return outpanel;
}
}
public static class PopulateLocBundleAction extends NodeAction {
private static final long serialVersionUID =3217348412906163076L;
public String getName () {
return "Populate bundle ...";
}
public HelpCtx getHelpCtx () {
return new HelpCtx ("org.netbeans.modules.apisupport.utils.tmplwizhlpr");
}
protected boolean enable (Node[] nodes) {
if (nodes.length == 0) return false;
for (int i = 0; i < nodes.length; i++)
if (! (nodes[i] instanceof Filter))
return false;
return true;
}
protected void performAction (Node[] nodes) {
PrintWriter pw = TopManager.getDefault ().getIO ("Populating Bundles", false).getOut ();
pw.println ("Processing localization bundles...");
for (int i = 0; i < nodes.length; i++)
process ((Filter) nodes[i], pw);
}
private void process (Filter f, PrintWriter pw) {
FileObject fo = f.dob.getPrimaryFile ();
String foName = fo.getPackageNameExt ('/', '.');
String bundleName = (String) fo.getAttribute (ATTR_BUNDLE);
if (bundleName == null) {
pw.println ("The file " + foName + " had no bundle set.");
} else {
pw.println ("File " + foName + " with bundle " + bundleName + "...");
// Note: this is the default locale bundle, intentionally.
String bundleResource = bundleName.replace ('.', '/') + ".properties";
FileObject bundle = TopManager.getDefault ().getRepository ().findResource (bundleResource);
if (bundle == null) {
pw.println ("The bundle named " + bundleResource + " could not be found.");
} else {
try {
InputStream is = bundle.getInputStream ();
String contains;
try {
Properties p = new Properties ();
p.load (is);
contains = p.getProperty (foName);
//pw.println ("Props dump:");
//p.list (pw);
} finally {
is.close ();
}
if (contains != null) {
pw.println ("Already had localized name: " + contains);
} else {
String locName = DataObject.find (fo).getNodeDelegate ().getDisplayName ();
pw.println ("Will add key for the localized name: " + locName);
File bundleF = NbClassPath.toFile (bundle);
if (bundleF == null) {
pw.println ("Sorry, only output to local-disk files is supported currently.");
} else {
FileLock lock = bundle.lock ();
try {
PrintStream ps = new PrintStream (new FileOutputStream (bundleF.toString (), true));
try {
// [PENDING] should ideally check for trailing newline...
ps.print (escape (foName));
ps.print ('=');
ps.println (locName);
} finally {
ps.close ();
}
} finally {
lock.releaseLock ();
}
}
}
} catch (IOException ioe) {
pw.println ("Caught exception:");
ioe.printStackTrace (pw);
}
}
}
f.fPC ("locName", null, null);
if (! f.isLeaf ()) {
Enumeration e = f.getChildren ().nodes ();
while (e.hasMoreElements ())
process ((Filter) e.nextElement (), pw);
}
}
private String escape (String text) {
// Escapes it for a props file.
StringBuffer escaped = new StringBuffer ();
int len = text.length ();
for (int i = 0; i < len; i++) {
char c = text.charAt (i);
if (Character.isWhitespace (c) || c == ':' || c == '=' || c == '#' || c == '!')
escaped.append ('\\');
escaped.append (c);
}
return escaped.toString ();
}
}
}
/*
* Log
* 11 Gandalf-post-FCS1.7.1.2 4/3/00 Jesse Glick Serializable dialog,
* hopefully.
* 10 Gandalf-post-FCS1.7.1.1 3/28/00 Jesse Glick SVUIDs.
* 9 Gandalf-post-FCS1.7.1.0 3/9/00 Jesse Glick Minor fixes.
* 8 Gandalf 1.7 1/18/00 Jesse Glick Fixed LocNameProp getter
* algorithm to load from Repo preferentially. Also fixed Generate action
* to use real node display name as sample, and not escape the value.
* 7 Gandalf 1.6 1/16/00 Jesse Glick Appending property keys.
* 6 Gandalf 1.5 1/14/00 Jesse Glick Support for system file
* system's name localizations.
* 5 Gandalf 1.4 12/22/99 Jesse Glick Oops, and forgot to set
* the top node too.
* 4 Gandalf 1.3 12/22/99 Jesse Glick Now shows a
* tree-structure useful for whole dirs full of templates.
* 3 Gandalf 1.2 12/22/99 Jesse Glick All right, now just
* enabled on any folder.
* 2 Gandalf 1.1 12/21/99 Jesse Glick Now active on
* non-templates if they are in the templates folder.
* 1 Gandalf 1.0 12/17/99 Jesse Glick
* $
*/